#include <stdio.h>
#include "defines.h"

/**
 * Forward declaration of our test functions
 */
void* testfunc1(void*);
void* testfunc2(void*);
void* testfunc3(void*);
void* testfunc4(void*);
void* testfunc5(void*);

/**
 *  Part D requires implementing the following functions:
 *
 *    int uthread_create(thread_id* t, void* (*start)(void*), void* args)
 *    int uthread_join(thread_id t, void** status)
 *    void uthread_exit(void* val_ptr)
 *    thread_id uthread_self()
 *    void uthread_yield()
 */
int main()
{
    // make room for a bunch of uthreads
    THREAD_T tid[32];

    // Test #1: Can I create a thread, join on it, and get its return value
    // when the thread uses uthread_exit?
    THREAD_CREATE(&tid[0], testfunc1, NULL);
    void* retval;
    THREAD_JOIN(tid[0], &retval);
    printf("retval: %d\n", retval);
    
    // Test #2: Does thread_self work?  Does lack of uthread_exit work?
    THREAD_CREATE(&tid[0], testfunc2, NULL);
    THREAD_CREATE(&tid[1], testfunc2, NULL);
    void* rv1;
    void* rv2;
    THREAD_JOIN(tid[0], &rv1);
    THREAD_JOIN(tid[1], &rv2);
    printf("return values are %d and %d\n", (int)rv1, (int)rv2);

    // Test #3: do parameters work?
    int i;
    for (i = 0; i < 4; ++i) {
        THREAD_CREATE(&tid[i], testfunc3, (void*)i);
    }
    for (i = 4; i > 0; --i) {
        void* rv;
        THREAD_JOIN(tid[i-1], &rv);
        printf("joined on thread %d with return value %d\n", i-1, (int)rv);
    }

    // Test #4: round robin scheduling.  Note that pthread does not guarantee
    // round-robin scheduling, so this test will produce different output
    // with pthreads...
    for (i = 0; i < 4; ++i) {
        THREAD_CREATE(&tid[i], testfunc4, (void*)i);
    }
    for (i = 0; i < 4; ++i) {
        THREAD_JOIN(tid[i], NULL);
    }

    // Test #5: uthread_self in parent, parent concurrent with children
    for (i = 1; i < 4; ++i) {
        THREAD_CREATE(&tid[i], testfunc5, (void*)i);
    }
    testfunc5(0);
    for (i = 1; i < 4; ++i) {
        THREAD_JOIN(tid[i], NULL);
    }

}

/**
 *  Demonstrate that the thread runs, and returns the right value
 */
void* testfunc1(void* params)
{
    printf("Hello, I am a uthread\n");
    THREAD_EXIT((void*)3);
}

/**
 * Demonstrate that thread_self works
 */
void* testfunc2(void* params)
{
    printf("Hello from thread %d\n", THREAD_SELF());
    return (void*)(THREAD_SELF() * 2);
}

/**
 * Test arguments
 */
void* testfunc3(void* params)
{
    printf("Hello from thread %d, arg=%d\n", THREAD_SELF(), (int)params);
    return (void*)(((int)params) * 2);
}

/**
 * Test round-robin scheduling
 */
void* testfunc4(void* params)
{
    int i;
    for (i = 0; i < 5; ++i) {
        printf("Hello from thread %d\n", (int)params);
        THREAD_YIELD();
    }
    return NULL;
}

/**
 * Test parent running with child
 */
void* testfunc5(void* params)
{
    // use this integer to coordinate among threads
    static volatile int pingpong = 0;
    int i;
    for (i = 0; i < 5; ++i) {
        while ((pingpong%4) != (int)params)
            THREAD_YIELD();
        printf("Pingpong: thread %d (ID=%d) sees %d\n", (int)params, THREAD_SELF(), pingpong);
        pingpong++;
    }
    return NULL;
}
